
#include <clock/clock.h>
#include <cspace/cspace.h>
#include "../bitset.h"
#include "../process.h"
#include "../ut_manager/ut.h"
#include "../vfs.h"
#include "../vmem_layout.h"

#include "../debug.h"
#define verbose SOS_DEBUG_PROC_DELETE
#include <sys/debug.h>
#include <sys/panic.h>

extern sos_pcb *_process_table;
extern sos_vfs *vfs;
extern int _num_procs;
extern bitset *pids;

static int _delete_tcb(sos_pcb *pcb);
static int _close_fds(sos_pcb *pcb);

int process_delete(sos_pcb *pcb, process_delete_cb cb, void *cookie) {
    dprintf(5, "process_delete: deleting PID %d\n", pcb->pid);

    int err = 0;
    // Only kill a process that is running - not allowed during creation
    if (!(pcb->running) && !(pcb->abort)) {
        dprintf(6, "process_delete: process is not running yet\n");
        if (cb) (*cb)(SOS_PROCESS_INVAL, cookie);
        return err;
    }

    // Suspend the thread
    if (pcb->tcb_cap) seL4_TCB_Suspend(pcb->tcb_cap);

    // Delete from process table
    if (pcb->pid) HASH_DEL(_process_table, pcb);

    // Return its PID to the bitset
    if (pcb->pid) bitset_clear(pids, (pcb->pid) - SOS_PID_MIN);

    // The process could be usleeping - revoke any pending timers
    if (pcb->sleep_timer > 0) {
        dprintf(6, "process_delete: revoking any pending timers\n");
        err = remove_timer(pcb->sleep_timer);
        cspace_free_slot(cur_cspace, pcb->reply_cap);
        pcb->sleep_timer = 0;
        pcb->reply_cap = 0;
        if (err) dprintf(6, "process_delete: could not delete timer\n");
    }

    // For all FDs in the FD table, go through and close resources
    // Destroy whole FD table
    err = _close_fds(pcb);
    if (err) dprintf(6, "process_delete: failed to clean up FDs (%d)\n", err);

    // Unpin the IPC buffer
    if (pcb->ipc_cap) {
        err = sos_page_release(pcb, SOS_PROCESS_IPC_BUFFER);
        if (err) dprintf(6, "process_delete: failed to release IPC buffer\n");
    }

    // Destroy the VM subsystem and all associated resources
    dprintf(6, "process_delete: destroying pager\n");
    err |= sos_pager_destroy(pcb);
    if (err) dprintf(6, "process_delete: failed to destroy pager (%d)\n", err);

    // Destroy TCB
    dprintf(6, "process_delete: destroying TCB\n");
    err = _delete_tcb(pcb);
    if (err) dprintf(6, "process_delete: failed to delete TCB (%d)\n", err);

    // Destroy cspace
    if (pcb->cspace) {
        dprintf(6, "process_delete: destroying cspace\n");
        err = cspace_destroy(pcb->cspace);
        pcb->cspace = 0;
        if (err) dprintf(6, "process_delete: failed to delete process cspace\n");
    }

    // Delete child cache
    if (pcb->children) {
        dprintf(6, "process_delete: destroying child PID cache\n");
        list_destroy(pcb->children);
    }

    // Notify waiters
    if (pcb->running) {
        dprintf(6, "process_delete: notifying waiting processes\n");
        err = process_resume_waiters(pcb);
        dprintf(6, "process_delete: notifying global-waiting processes\n");
        err |= process_resume_global_waiters(pcb);
        if (err) dprintf(6, "process_delete: failed to resume waiting processes\n");
    }

    // Free PCB if we can do it safely
    if (pcb->reply_cap == 0) {
        free(pcb);
    } else {
        // Some operation needs to clean up...
        pcb->abort = 1;
    }

    _num_procs--;

    // Call back if necessary
    if (cb != NULL) {
        dprintf(6, "process_delete: calling back (%p, %p)\n", cb, cookie);
        (*cb)(err, cookie);
    }
    return err;
}

static int _delete_tcb(sos_pcb *pcb) {
    int err;
    if (!(pcb->tcb_cap)) return 0;

    dprintf(6, "process_delete_delete_tcb: Deleting TCB for PID %d\n",
            pcb->pid);
    // remove tcb_cap, suspending the thread
    err = cspace_delete_cap(cur_cspace, pcb->tcb_cap);
    pcb->tcb_cap = 0;

    // return tcb_addr to ut
    ut_free(pcb->tcb_addr, seL4_TCBBits);
    pcb->tcb_addr = 0;

    return err;
}

static int _close_fds(sos_pcb *pcb) {
    int err = 0;
    sos_fd *fd, *tmp;

    HASH_ITER(hh, (pcb->fd_table), fd, tmp) {
        // Close FD with VFS
        err |= sos_vfs_close(pcb, vfs, fd->proc_fd);
    };

    return err;
}
